package utils;

import core.HiddenLayer;
import core.Phone;
import core.PhoneWrapper;
import core.Speaker;
import org.apache.commons.lang3.ArrayUtils;

import java.io.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 * User: lahiru
 * Date: 5/11/14
 * Time: 4:55 PM
 * To change this template use File | Settings | File Templates.
 */
public class CalculatePhonemeStats {
    private static final double log2 = Math.log(2);
    private static int phoneCount = 42;
    private static final int numberOfHiddenUnits = 2048;
    private static final double psiThreshold = 0.25;
    private static boolean euclidean = true;
    private static final String vowelIDs = "5 11 12 16 19 24 28 29 33 35 36 38 41 42";
    private static final boolean writeFilesForClustering = false;


    private static final String psiVectorDirectory = "/home/lahiru/workspace/arctic/psi/hiddenLayer";
    private static final String haVectorDirectory = "/home/lahiru/workspace/arctic/ha/hiddenLayer";
    private static final String location = "/media/Bunty/Academic/Research/speaker-effects/monophone/objects/";



    private static Map<Integer, Double[]> psiVectors = new HashMap<Integer, Double[]>();
    private static Map<Integer, Double[]> haVectors = new HashMap<Integer, Double[]>();

    private static Map<Integer, Map<Integer, Double>> h1PhoneDistances = new HashMap<Integer, Map<Integer, Double>>();        //Phoneme distances for each hidden layer
    private static Map<Integer, Map<Integer, Double>> h2PhoneDistances = new HashMap<Integer, Map<Integer, Double>>();
    private static Map<Integer, Map<Integer, Double>> h3PhoneDistances = new HashMap<Integer, Map<Integer, Double>>();
    private static Map<Integer, Map<Integer, Double>> h4PhoneDistances = new HashMap<Integer, Map<Integer, Double>>();
    private static Map<Integer, Map<Integer, Double>> h5PhoneDistances = new HashMap<Integer, Map<Integer, Double>>();
    private static Map<Integer, Map<Integer, Double>> h6PhoneDistances = new HashMap<Integer, Map<Integer, Double>>();


    private static double h1TotalPhoneDistance = 0;
    private static double h2TotalPhoneDistance = 0;
    private static double h3TotalPhoneDistance = 0;
    private static double h4TotalPhoneDistance = 0;
    private static double h5TotalPhoneDistance = 0;
    private static double h6TotalPhoneDistance = 0;


    private static Map<Integer, Map<Integer, Double>> h1PsiDistances = new HashMap<Integer, Map<Integer, Double>>();        //Phoneme distances for each hidden layer
    private static Map<Integer, Map<Integer, Double>> h2PsiDistances = new HashMap<Integer, Map<Integer, Double>>();
    private static Map<Integer, Map<Integer, Double>> h3PsiDistances = new HashMap<Integer, Map<Integer, Double>>();
    private static Map<Integer, Map<Integer, Double>> h4PsiDistances = new HashMap<Integer, Map<Integer, Double>>();
    private static Map<Integer, Map<Integer, Double>> h5PsiDistances = new HashMap<Integer, Map<Integer, Double>>();
    private static Map<Integer, Map<Integer, Double>> h6PsiDistances = new HashMap<Integer, Map<Integer, Double>>();


    private static double h1TotalPsiDistance = 0;
    private static double h2TotalPsiDistance = 0;
    private static double h3TotalPsiDistance = 0;
    private static double h4TotalPsiDistance = 0;
    private static double h5TotalPsiDistance = 0;
    private static double h6TotalPsiDistance = 0;


    private static Map<Integer, Map<Integer, Double>> h1WeightedDistances = new HashMap<Integer, Map<Integer, Double>>();        //Weighted Phoneme distances for each hidden layer
    private static Map<Integer, Map<Integer, Double>> h2WeightedDistances = new HashMap<Integer, Map<Integer, Double>>();
    private static Map<Integer, Map<Integer, Double>> h3WeightedDistances = new HashMap<Integer, Map<Integer, Double>>();
    private static Map<Integer, Map<Integer, Double>> h4WeightedDistances = new HashMap<Integer, Map<Integer, Double>>();
    private static Map<Integer, Map<Integer, Double>> h5WeightedDistances = new HashMap<Integer, Map<Integer, Double>>();
    private static Map<Integer, Map<Integer, Double>> h6WeightedDistances = new HashMap<Integer, Map<Integer, Double>>();


    private static double h1TotalWeightedDistance = 0;
    private static double h2TotalWeightedDistance = 0;
    private static double h3TotalWeightedDistance = 0;
    private static double h4TotalWeightedDistance = 0;
    private static double h5TotalWeightedDistance = 0;
    private static double h6TotalWeightedDistance = 0;


    public static void main(String[] args) {

        try {

//            System.out.println("Processing Hidden Layer 6");
//            FileInputStream fileIn = new FileInputStream(location + "h6.ser");
//            ObjectInputStream in = new ObjectInputStream(fileIn);
//            core.HiddenLayer hiddenLayer = (core.HiddenLayer) in.readObject();
//            accumilatePhoneDifferences(hiddenLayer);

            System.out.println("Processing Hidden Layer 1");
            //read the hiddenlayer1
            FileInputStream fileIn = new FileInputStream(location + "h1.ser");
            ObjectInputStream in = new ObjectInputStream(fileIn);
            HiddenLayer hiddenLayer = (HiddenLayer) in.readObject();
            accumilatePhoneDifferences(hiddenLayer);

//            System.exit(0);

            //read the hiddenlayer2
            System.out.println("Processing Hidden Layer 2");
            fileIn = new FileInputStream(location + "h2.ser");
            in = new ObjectInputStream(fileIn);
            hiddenLayer = (HiddenLayer) in.readObject();
            accumilatePhoneDifferences(hiddenLayer);

            //read the hiddenlayer3
            System.out.println("Processing Hidden Layer 3");
            fileIn = new FileInputStream(location + "h3.ser");
            in = new ObjectInputStream(fileIn);
            hiddenLayer = (HiddenLayer) in.readObject();
            accumilatePhoneDifferences(hiddenLayer);

            //read the hiddenlayer4
            System.out.println("Processing Hidden Layer 4");
            fileIn = new FileInputStream(location + "h4.ser");
            in = new ObjectInputStream(fileIn);
            hiddenLayer = (HiddenLayer) in.readObject();
            accumilatePhoneDifferences(hiddenLayer);

            //read the hiddenlayer5
            System.out.println("Processing Hidden Layer 5");
            fileIn = new FileInputStream(location + "h5.ser");
            in = new ObjectInputStream(fileIn);
            hiddenLayer = (HiddenLayer) in.readObject();
            accumilatePhoneDifferences(hiddenLayer);


            //read the hiddenlayer6
            System.out.println("Processing Hidden Layer 6");
            fileIn = new FileInputStream(location + "h6.ser");
            in = new ObjectInputStream(fileIn);
            hiddenLayer = (HiddenLayer) in.readObject();
            accumilatePhoneDifferences(hiddenLayer);

            System.out.println("##########  Distances between HA Phonetic Vectors ##############");
            printPhoneHaDistances();
//            System.out.println("##########  Distances between PSI Phonetic Vectors ##############");
//            printPhonePsiDistances();
//            System.out.println("##########  Distances between Weighted Phonetic Vectors ##############");
//            printPhoneWeighedDistances();


//            System.out.println("##########  Vowel Distances between HA Phonetic Vectors ##############");
            /*
            Print vowel distances for Ha distances
             */
//            calculateVowelDistances(h1PhoneDistances, 1);
//            calculateVowelDistances(h2PhoneDistances, 2);
//            calculateVowelDistances(h3PhoneDistances, 3);
//            calculateVowelDistances(h4PhoneDistances, 4);
//            calculateVowelDistances(h5PhoneDistances, 5);
//            calculateVowelDistances(h6PhoneDistances, 6);


//            System.out.println("##########  Vowel Distances between PSI Phonetic Vectors ##############");
//            /*
//            Print vowel distances for PSI distances
//             */
//            calculateVowelDistances(h1PsiDistances, 1);
//            calculateVowelDistances(h2PsiDistances, 2);
//            calculateVowelDistances(h3PsiDistances, 3);
//            calculateVowelDistances(h4PsiDistances, 4);
//            calculateVowelDistances(h5PsiDistances, 5);
//            calculateVowelDistances(h6PsiDistances, 6);


//            System.out.println("##########  Vowel Distances between Weighted Phonetic Vectors ##############");
//            /*
//            Print vowel distances for PSI distances
//             */
//            calculateVowelDistances(h1WeightedDistances, 1);
//            calculateVowelDistances(h2WeightedDistances, 2);
//            calculateVowelDistances(h3WeightedDistances, 3);
//            calculateVowelDistances(h4WeightedDistances, 4);
//            calculateVowelDistances(h5WeightedDistances, 5);
//            calculateVowelDistances(h6WeightedDistances, 6);


            in.close();
            fileIn.close();
        } catch (IOException i) {
            i.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }


    private static void accumilatePhoneDifferences(HiddenLayer hiddenLayer) throws IOException {

        Map<Integer, PhoneWrapper> phoneWrapperMap = new HashMap<Integer, PhoneWrapper>();

        for (Speaker speaker : hiddenLayer.getSpeakerList()) {
            Map<Integer, Phone> phones = speaker.getPhones();
            for (int i = 1; i <= phones.size(); i++) {
                Phone phone;
                if (phones.get(i) == null) {
                    continue;
                }
                phone = phones.get(i);
                int phoneID = phone.getId();

                if (phoneWrapperMap.get(phoneID) == null) {
                    phoneWrapperMap.put(phoneID, new PhoneWrapper(phone));
                } else {
                    PhoneWrapper phoneWrapper = phoneWrapperMap.get(phoneID);
                    phoneWrapper.addActivations(phone.getVector());
                    phoneWrapper.increaseCount(phone.getCount());
                    phoneWrapperMap.put(phone.getId(), phoneWrapper);
                }
            }
        }

        Object[] tempPhoneIDs = phoneWrapperMap.keySet().toArray();
//        System.out.println("Before");
//        for(Object o : tempPhoneIDs){
//            System.out.println(o.toString());
//        }

        Arrays.sort(tempPhoneIDs);

//        System.out.println("After");
//        for(Object o : tempPhoneIDs){
//            System.out.println(o.toString());
//        }

        List<Object> phoneIdList = Arrays.asList(tempPhoneIDs);

//        Average all the phonemeHiddenActivation Vectors
        for (int i = 0; i < phoneIdList.size(); i++) {
            int phoneID = (Integer) phoneIdList.get(i);
            PhoneWrapper phoneWrapper = phoneWrapperMap.get(phoneID);
            phoneWrapper.averageVector();
            phoneWrapperMap.put(phoneID, phoneWrapper);
            haVectors.put(phoneID,ArrayUtils.toObject(phoneWrapper.getVector()));
        }

        accumilatePhoneticHaDistances(hiddenLayer.getId(), phoneWrapperMap, phoneIdList);
//        accumilatePhoneticPsiDistances(hiddenLayer.getId(), phoneWrapperMap, phoneIdList);       //todo uncomment
//        accumilatePhoneticWeighedDistances(hiddenLayer.getId(), phoneWrapperMap, phoneIdList);

        if(writeFilesForClustering){
            writePSIVectorsForWeka(hiddenLayer.getId(), phoneIdList);
            writeHaVectorsForWeka(hiddenLayer.getId(),phoneWrapperMap,phoneIdList);
        }

    }

    private static void accumilatePhoneticWeighedDistances(int hiddenLayerId, Map<Integer, PhoneWrapper> phoneWrapperMap, List<Object> phoneIdList) {
        double totalDistance = 0.0;

        for (int i = 0; i < phoneIdList.size(); i++) {

            Map<Integer, Double> distanceVector = new HashMap<Integer, Double>();

            int phoneID1 = (Integer) phoneIdList.get(i);
            double[] psi1 = ArrayUtils.toPrimitive(psiVectors.get(phoneID1));

            PhoneWrapper phoneWrapper1 = phoneWrapperMap.get(phoneID1);
            for (int j = 0; j < phoneIdList.size(); j++) {
                int phoneID2 = (Integer) phoneIdList.get(j);
                PhoneWrapper phoneWrapper2 = phoneWrapperMap.get(phoneID2);
                double[] psi2 = ArrayUtils.toPrimitive(psiVectors.get(phoneID2));

                //Calculate the distance between two phones
                double distance;
                if (euclidean) {
                    distance = calculateWeightedEuclideanDistance(phoneWrapper1.getVector(), phoneWrapper2.getVector(), psi1, psi2);
                } else {
                    distance = calculateKLDivergenceVectors(phoneWrapper1.getVector(), phoneWrapper2.getVector());   //todo change to the weighted version
                }
                distanceVector.put(phoneID2, distance);
                totalDistance = totalDistance + distance;
            }
            if (hiddenLayerId == 1) {
                h1WeightedDistances.put(phoneID1, distanceVector);
            } else if (hiddenLayerId == 2) {
                h2WeightedDistances.put(phoneID1, distanceVector);
            } else if (hiddenLayerId == 3) {
                h3WeightedDistances.put(phoneID1, distanceVector);
            } else if (hiddenLayerId == 4) {
                h4WeightedDistances.put(phoneID1, distanceVector);
            } else if (hiddenLayerId == 5) {
                h5WeightedDistances.put(phoneID1, distanceVector);
            } else if (hiddenLayerId == 6) {
                h6WeightedDistances.put(phoneID1, distanceVector);
            }
        }

        if (hiddenLayerId == 1) {
            h1TotalWeightedDistance = totalDistance;
        } else if (hiddenLayerId == 2) {
            h2TotalWeightedDistance = totalDistance;
        } else if (hiddenLayerId == 3) {
            h3TotalWeightedDistance = totalDistance;
        } else if (hiddenLayerId == 4) {
            h4TotalWeightedDistance = totalDistance;
        } else if (hiddenLayerId == 5) {
            h5TotalWeightedDistance = totalDistance;
        } else if (hiddenLayerId == 6) {
            h6TotalWeightedDistance = totalDistance;
        }
    }

    private static void writeHaVectorsForWeka(int hiddenLayerID, Map<Integer, PhoneWrapper> phoneWrapperMap, List<Object> phoneIdList) throws IOException {
        //start writing the arff file for weka clustering
        FileWriter fileWriter = new FileWriter(haVectorDirectory + hiddenLayerID + ".arff");
        BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);

        bufferedWriter.write("@relation phonemes");
        bufferedWriter.newLine();
        bufferedWriter.newLine();

        //write about attributes
        int numAttributes = numberOfHiddenUnits;
        for (int h = 1; h <= numAttributes; h++) {
            bufferedWriter.write("@attribute h" + h + " numeric\n");
        }
        bufferedWriter.newLine();
        bufferedWriter.newLine();

        bufferedWriter.write("@data\n");
        bufferedWriter.newLine();
        bufferedWriter.newLine();


        for (int i = 0; i < phoneIdList.size(); i++) {
            int phoneID1 = (Integer) phoneIdList.get(i);
            StringBuffer sb = new StringBuffer();
            double[] vector = phoneWrapperMap.get(phoneID1).getVector();
            for (int p = 0; p < numAttributes; p++) {
                if (p == numAttributes - 1) {
                    sb.append(vector[p] + "\n");
                } else {
                    sb.append(vector[p] + ",");
                }
            }
            bufferedWriter.write(sb.toString());

        }
        bufferedWriter.close();
    }


    private static void accumilatePhoneticPsiDistances(int hiddenLayerId, Map<Integer, PhoneWrapper> phoneWrapperMap, List<Object> phoneIdList) {
        double totalDistance = 0.0;

        for (int i = 0; i < phoneIdList.size(); i++) {
            int phoneID1 = (Integer) phoneIdList.get(i);
            double[] vector1 = phoneWrapperMap.get(phoneID1).getVector();
            int numHiddenUnits = vector1.length;
            Double[] psi = new Double[numHiddenUnits];
            for (int k = 0; k < numHiddenUnits; k++) {     //initially all the elements are zero
                psi[k] = 0.0;
            }

            for (int j = 0; j < phoneIdList.size(); j++) {
                int phoneID2 = (Integer) phoneIdList.get(j);
                double[] vector2 = phoneWrapperMap.get(phoneID2).getVector();
                for (int k = 0; k < numHiddenUnits; k++) {
                    if (Math.abs(vector1[k] - vector2[k]) > psiThreshold) {
                        psi[k] = psi[k] + 1.0;
                    }
                }
            }
            psiVectors.put(phoneID1, psi);
        }


        for (int i = 0; i < phoneIdList.size(); i++) {
            Map<Integer, Double> distanceVector = new HashMap<Integer, Double>();
            int phoneID1 = (Integer) phoneIdList.get(i);
            for (int j = 0; j < phoneIdList.size(); j++) {
                int phoneID2 = (Integer) phoneIdList.get(j);
                //Calculate the distance between two phones
                double distance = calculateEuclideanDistance(ArrayUtils.toPrimitive(psiVectors.get(phoneID1)), ArrayUtils.toPrimitive(psiVectors.get(phoneID2)));
                distanceVector.put(phoneID2, distance);
                totalDistance = totalDistance + distance;
            }
            if (hiddenLayerId == 1) {
                h1PsiDistances.put(phoneID1, distanceVector);
            } else if (hiddenLayerId == 2) {
                h2PsiDistances.put(phoneID1, distanceVector);
            } else if (hiddenLayerId == 3) {
                h3PsiDistances.put(phoneID1, distanceVector);
            } else if (hiddenLayerId == 4) {
                h4PsiDistances.put(phoneID1, distanceVector);
            } else if (hiddenLayerId == 5) {
                h5PsiDistances.put(phoneID1, distanceVector);
            } else if (hiddenLayerId == 6) {
                h6PsiDistances.put(phoneID1, distanceVector);
            }
        }

        if (hiddenLayerId == 1) {
            h1TotalPsiDistance = totalDistance;
        } else if (hiddenLayerId == 2) {
            h2TotalPsiDistance = totalDistance;
        } else if (hiddenLayerId == 3) {
            h3TotalPsiDistance = totalDistance;
        } else if (hiddenLayerId == 4) {
            h4TotalPsiDistance = totalDistance;
        } else if (hiddenLayerId == 5) {
            h5TotalPsiDistance = totalDistance;
        } else if (hiddenLayerId == 6) {
            h6TotalPsiDistance = totalDistance;
        }
    }

    private static void accumilatePhoneticHaDistances(int hiddenLayerId, Map<Integer, PhoneWrapper> phoneWrapperMap, List<Object> phoneIdList) {
        double totalDistance = 0.0;

        for (int i = 0; i < phoneIdList.size(); i++) {

            Map<Integer, Double> distanceVector = new HashMap<Integer, Double>();

            int phoneID1 = (Integer) phoneIdList.get(i);

            PhoneWrapper phoneWrapper1 = phoneWrapperMap.get(phoneID1);
            for (int j = 0; j < phoneIdList.size(); j++) {
                int phoneID2 = (Integer) phoneIdList.get(j);
                PhoneWrapper phoneWrapper2 = phoneWrapperMap.get(phoneID2);

                //Calculate the distance between two phones
                double distance;
                if (euclidean) {
                    distance = calculateEuclideanDistance(phoneWrapper1.getVector(), phoneWrapper2.getVector());
                } else {
                    distance = calculateKLDivergenceVectors(phoneWrapper1.getVector(), phoneWrapper2.getVector());
                }
                distanceVector.put(phoneID2, distance);
                totalDistance = totalDistance + distance;
            }
            if (hiddenLayerId == 1) {
                h1PhoneDistances.put(phoneID1, distanceVector);
            } else if (hiddenLayerId == 2) {
                h2PhoneDistances.put(phoneID1, distanceVector);
            } else if (hiddenLayerId == 3) {
                h3PhoneDistances.put(phoneID1, distanceVector);
            } else if (hiddenLayerId == 4) {
                h4PhoneDistances.put(phoneID1, distanceVector);
            } else if (hiddenLayerId == 5) {
                h5PhoneDistances.put(phoneID1, distanceVector);
            } else if (hiddenLayerId == 6) {
                h6PhoneDistances.put(phoneID1, distanceVector);
            }
        }

        if (hiddenLayerId == 1) {
            h1TotalPhoneDistance = totalDistance;
        } else if (hiddenLayerId == 2) {
            h2TotalPhoneDistance = totalDistance;
        } else if (hiddenLayerId == 3) {
            h3TotalPhoneDistance = totalDistance;
        } else if (hiddenLayerId == 4) {
            h4TotalPhoneDistance = totalDistance;
        } else if (hiddenLayerId == 5) {
            h5TotalPhoneDistance = totalDistance;
        } else if (hiddenLayerId == 6) {
            h6TotalPhoneDistance = totalDistance;
        }
    }


    private static void writePSIVectorsForWeka(int hiddenLayerID, List<Object> phoneIdList) throws IOException {
        //start writing the arff file for weka clustering
        FileWriter fileWriter = new FileWriter(psiVectorDirectory + hiddenLayerID + ".arff");
        BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);

        bufferedWriter.write("@relation phonemes");
        bufferedWriter.newLine();
        bufferedWriter.newLine();

        //write about attributes
        int numAttributes = numberOfHiddenUnits;
        for (int h = 1; h <= numAttributes; h++) {
            bufferedWriter.write("@attribute h" + h + " numeric\n");
        }
        bufferedWriter.newLine();
        bufferedWriter.newLine();

        bufferedWriter.write("@data\n");
        bufferedWriter.newLine();
        bufferedWriter.newLine();


        for (int i = 0; i < phoneIdList.size(); i++) {
            StringBuffer sb = new StringBuffer();
            int phoneID1 = (Integer) phoneIdList.get(i);
            Double[] vector = psiVectors.get(phoneID1);
            for (int p = 0; p < numAttributes; p++) {
                if (p == numAttributes - 1) {
                    sb.append(vector[p] + "\n");
                } else {
                    sb.append(vector[p] + ",");
                }
            }
            bufferedWriter.write(sb.toString());

        }
        bufferedWriter.close();
    }


    private static void calculateVowelDistances(Map<Integer, Map<Integer, Double>> phoneDistances, int layerID) {
        List<String> vowels = (Arrays.asList(vowelIDs.split(" ")));
        int comparisons = 0;
        double totalDistance = 0.0;
        for (int i = 0; i < vowels.size(); i++) {
            int phoneID1 = Integer.parseInt(vowels.get(i));
            Map<Integer, Double> distances = phoneDistances.get(phoneID1);
            double interDistance = 0.0;

            for (int j = i + 1; j < vowels.size(); j++) {
                comparisons = comparisons + 1;
                int phoneID2 = Integer.parseInt(vowels.get(j));
                interDistance = interDistance + distances.get(phoneID2);
            }
            totalDistance = totalDistance + interDistance;
        }
        System.out.println("The total distances between vowels for layer " + layerID + " is " + totalDistance);
    }


    private static void printPhoneHaDistances() {
        System.out.println("Total core.Phone Distance for Hidden layer 1 " + h1TotalPhoneDistance);
        System.out.println("Average core.Phone Distance for Hidden layer 1 " + h1TotalPhoneDistance / phoneCount);

        System.out.println("Total core.Phone Distance for Hidden layer 2 " + h2TotalPhoneDistance);
        System.out.println("Average core.Phone Distance for Hidden layer 2 " + h2TotalPhoneDistance / phoneCount);

        System.out.println("Total core.Phone Distance for Hidden layer 3 " + h3TotalPhoneDistance);
        System.out.println("Average core.Phone Distance for Hidden layer 3 " + h3TotalPhoneDistance / phoneCount);

        System.out.println("Total core.Phone Distance for Hidden layer 4 " + h4TotalPhoneDistance);
        System.out.println("Average core.Phone Distance for Hidden layer 4 " + h4TotalPhoneDistance / phoneCount);

        System.out.println("Total core.Phone Distance for Hidden layer 5 " + h5TotalPhoneDistance);
        System.out.println("Average core.Phone Distance for Hidden layer 5 " + h5TotalPhoneDistance / phoneCount);

        System.out.println("Total core.Phone Distance for Hidden layer 6 " + h6TotalPhoneDistance);
        System.out.println("Average core.Phone Distance for Hidden layer 6 " + h6TotalPhoneDistance / phoneCount);

    }


    private static void printPhonePsiDistances() {
        System.out.println("Total PSI Distance for Hidden layer 1 " + h1TotalPsiDistance);
        System.out.println("Average PSI Distance for Hidden layer 1 " + h1TotalPsiDistance / phoneCount);

        System.out.println("Total PSI Distance for Hidden layer 2 " + h2TotalPsiDistance);
        System.out.println("Average PSI Distance for Hidden layer 2 " + h2TotalPsiDistance / phoneCount);

        System.out.println("Total PSI Distance for Hidden layer 3 " + h3TotalPsiDistance);
        System.out.println("Average PSI Distance for Hidden layer 3 " + h3TotalPsiDistance / phoneCount);

        System.out.println("Total PSI Distance for Hidden layer 4 " + h4TotalPsiDistance);
        System.out.println("Average PSI Distance for Hidden layer 4 " + h4TotalPsiDistance / phoneCount);

        System.out.println("Total PSI Distance for Hidden layer 5 " + h5TotalPsiDistance);
        System.out.println("Average PSI Distance for Hidden layer 5 " + h5TotalPsiDistance / phoneCount);

        System.out.println("Total PSI Distance for Hidden layer 6 " + h6TotalPsiDistance);
        System.out.println("Average PSI Distance for Hidden layer 6 " + h6TotalPsiDistance / phoneCount);

    }


    private static void printPhoneWeighedDistances() {
        System.out.println("Total Weighed Distance for Hidden layer 1 " + h1TotalWeightedDistance);
        System.out.println("Average Weighed Distance for Hidden layer 1 " + h1TotalWeightedDistance / phoneCount);

        System.out.println("Total Weighed Distance for Hidden layer 2 " + h2TotalWeightedDistance);
        System.out.println("Average Weighed Distance for Hidden layer 2 " + h2TotalWeightedDistance / phoneCount);

        System.out.println("Total Weighed Distance for Hidden layer 3 " + h3TotalWeightedDistance);
        System.out.println("Average Weighed Distance for Hidden layer 3 " + h3TotalWeightedDistance / phoneCount);

        System.out.println("Total Weighed Distance for Hidden layer 4 " + h4TotalWeightedDistance);
        System.out.println("Average Weighed Distance for Hidden layer 4 " + h4TotalWeightedDistance / phoneCount);

        System.out.println("Total Weighed Distance for Hidden layer 5 " + h5TotalWeightedDistance);
        System.out.println("Average Weighed Distance for Hidden layer 5 " + h5TotalWeightedDistance / phoneCount);

        System.out.println("Total Weighed Distance for Hidden layer 6 " + h6TotalWeightedDistance);
        System.out.println("Average Weighed Distance for Hidden layer 6 " + h6TotalWeightedDistance / phoneCount);

    }


    public static double calculateKLDivergenceVectors(double[] vector1, double[] vector2) throws NumberFormatException {
        int length = vector1.length;
        if (length != vector2.length) {
            throw new NumberFormatException("Vector length's are not matching ");
        }

        if (vector1.length != numberOfHiddenUnits) {
            System.out.println("vector lengths are wrong ");
        }

        double kLdiv = 0.0;
        for (int i = 0; i < length; i++) {

            String vector1s = vector1[i] + "";
            String vector2s = vector2[i] + "";

            if (vector1s.equals("NaN")) {
                System.out.println("The vector1 is NaN " + i);
            }

            if (vector2s.equals("NaN")) {
                System.out.println("The vector2 is NaN " + i);
            }

            double temp = klDivergence(vector1[i], vector2[i]);
            String temps = temp + "";
            if (temps.equals("NaN")) {
//                System.out.println("The temp is NaN " + i);
//                System.out.println(vector1[i]);
//                System.out.println(vector2[i]);
                temp = 0.0; //todo change
            }


//            kLdiv = kLdiv + klDivergence(vector1[i], vector2[i]);
            kLdiv = kLdiv + temp;
//            System.out.println("temp " + temp);
        }

        return kLdiv / length;  //To change body of created methods use File | Settings | File Templates.
    }


    public static double calculateEuclideanDistance(double[] array1, double[] array2) {
        int length = array1.length;
        if (length != array2.length) {
            throw new NumberFormatException("Vector length's are not matching ");
        }

        double Sum = 0.0;                                                   //todo I change the euclidean distance
//        for (int i = 0; i < array1.length; i++) {
//            Sum = Sum + Math.pow((array1[i] - array2[i]), 2.0);
//        }

        for (int i = 0; i < length; i++) {
            for(int j=0; j<length; j++){
                Sum = Sum + Math.pow((array1[i] - array2[j]), 2.0);
            }
            Sum = Sum + Math.pow((array1[i] - array2[i]), 2.0);
        }

        return Math.sqrt(Sum);
    }


    public static double calculateWeightedEuclideanDistance(double[] array1, double[] array2, double[] psi1, double[] psi2) {
        int length = array1.length;
        if (length != array2.length && length != psi1.length && length != psi2.length) {
            throw new NumberFormatException("Vector length's are not matching ");
        }

        double Sum = 0.0;
        for (int i = 0; i < array1.length; i++) {
//            double weight = Math.abs(psi1[i] - psi2[i]);
            double weight = psi1[i] + psi2[i];
            Sum = Sum + weight * Math.pow((array1[i] - array2[i]), 2.0);
//            Sum = Sum + Math.pow((psi1[i] * array1[i] - psi2[i] * array2[i]), 2.0);
        }
        return Math.sqrt(Sum);
    }

    public static double klDivergence(double p1, double p2) {


        double klDiv = 0.0;

        if (p1 == 0.0 || p2 == 0.0) {
            return klDiv;
        } else {
//            return ((p1 * Math.log(p1 / p2)) + ((1 - p1) * Math.log((1 - p1) / (1 - p2)))) / log2;
            return (((p1 * Math.log(p1 / p2)) + ((1 - p1) * Math.log((1 - p1) / (1 - p2)))) + ((p2 * Math.log(p2 / p1)) + ((1 - p2) * Math.log((1 - p2) / (1 - p1))))) / log2;
        }

    }

    /*
    This method prints the coverage of phonemes for each speaker with the number of frames
     */
    private static void printPhonemCoverage(HiddenLayer hiddenLayer) {
        StringBuffer coverageReport = new StringBuffer();
        coverageReport.append("#############  Phoneme Coverage ################## \n");
        coverageReport.append("core.Speaker ID ,");
        for (int i = 1; i <= 42; i++) {
            coverageReport.append("core.Phone " + i + ",");
        }
        coverageReport.append("\n");

        for (Speaker speaker : hiddenLayer.getSpeakerList()) {
            coverageReport.append("core.Speaker " + speaker.getId() + ",");
            for (int i = 1; i <= 42; i++) {
                int count = 0;
                Phone phone = speaker.getPhones().get(i);
                if (phone != null) {
                    count = phone.getCount();
                }
                coverageReport.append(count + ",");
            }
            coverageReport.append("\n");
        }

        System.out.println(coverageReport.toString());

    }


}
